/**
 * This class is for synchronising a number of elements that appear on the view
 * against the model.
 * <p>
 * 
 * If the state of a gadget was updated, some elements might have been deleted,
 * while others were added.
 * <p>
 * 
 * To avoid recreation of the whole view, we simply delete elements from the
 * view and add new ones.
 * <p>
 * 
 * For sorted synchronisation, use SortedItemSync.
 * <p>
 * 
 * 
 * @param elementSelector
 *                A JQuery string that returns all elements.
 * @param insertCallback
 *                A function that inserts a missing element. It must havbe two
 *                parameters, id (the DOM id) and data (the data given by you to
 *                the syncElements function).
 * @return
 */

function ItemSync(elementSelector, insertCallback) {
    this.elementSelector = elementSelector;
    this.insertElement = insertCallback;

    this.syncElements = method_syncElements;

    this.removeOldElements = method_removeElements;

    this._findAndRemoveOldIds = method_findAndRemoveOldIds;
    this._getMissingIds = method_getMissingIds;
    this._insertMissingElements = method_insertMissingElements;
}

/**
 * This class is for synchronising a number of SORTED elements that appear on
 * the view against the model.
 * <p>
 * 
 * If the state of a gadget was updated, some elements might have been deleted,
 * while others were added.
 * <p>
 * 
 * To avoid recreation of the whole view, we simply delete elements from the
 * view and add new ones.
 * <p>
 * 
 * 
 * @param elementSelector
 *                A JQuery string that returns all elements.
 * @param insertCallback
 *                A function that inserts a missing element. It must have three
 *                parameters, id (the DOM id), data (the data given by you to
 *                the syncElements function) and previousSibling (that means we
 *                should insert this element behind previousSibling, and if it
 *                is null, we should insert it at the very beginning).
 * @return
 */
function SortedItemSync(elementSelector, insertCallback) {

    this.elementSelector = elementSelector;
    this.insertElement = insertCallback;

    this.removeOldElements = method_removeElements;

    this._findAndRemoveOldIds = method_findAndRemoveOldIds;
    this._getMissingIds = method_getMissingIds;


    this._insertMissingElements = method_insertMissingElementsSorted;
    this._getPreviousSiblingOf = method_getPreviousSiblingOf;

    this.syncElements = method_syncElementsSorted;

    this.allIdsSorted = null;
    this._fillAllIds = method_fillAllIdsSorted;

}


/**
 * Synchronise the elements that exist now against a new list with elements.
 * 
 * @param newElementIdMap
 *                A map with <code>newElementIdList[id] = data</code>, where
 *                <code>id</code> is the id of the element in the DOM, and
 *                <code>data</code> contains anything that helps the
 *                createElement function to create the new element and insert it
 *                at the corrept place.
 * @return
 */
function method_syncElementsSorted(newElementIdMap) {
    this._findAndRemoveOldIds(newElementIdMap);
    var missingIdsMap = this._getMissingIds(newElementIdMap);
    this._fillAllIds(newElementIdMap);
    this._insertMissingElements(missingIdsMap);
}


/**
 * Synchronise the elements that exist now against a new list with elements.
 * 
 * @param newElementIdMap
 *                A map with <code>newElementIdList[id] = data</code>, where
 *                <code>id</code> is the id of the element in the DOM, and
 *                <code>data</code> contains anything that helps the
 *                createElement function to create the new element and insert it
 *                at the corrept place.
 * @return
 */
function method_syncElements(newElementIdMap) {
    this._findAndRemoveOldIds(newElementIdMap);
    var missingIdsMap = this._getMissingIds(newElementIdMap);
    this._insertMissingElements(missingIdsMap);
}

/**
 * Override this method to provide your own way to remove these elements.
 * 
 * @param selectedElements
 *                A JQuery object containing all the elements that need to be
 *                removed.
 * @return
 */
function method_removeElements(selectedElements) {
    selectedElements.remove();
}

function method_findAndRemoveOldIds(newElementIdMap) {
    var subselector = '';
    for ( var currentId in newElementIdMap) {
	subselector += "[id!='" + currentId + "']";
    }
    var oldElements = $(this.elementSelector).filter(subselector);
    this.removeOldElements(oldElements);
}

function method_getMissingIds(newElementIdMap) {
    var missingIdsMap = new Object();

    for ( var currentId in newElementIdMap) {
	if (!document.getElementById(currentId)) {
	    missingIdsMap[currentId] = newElementIdMap[currentId];
	}
    }
    return missingIdsMap;
}

function method_insertMissingElements(missingIdsMap) {
    for ( var currentId in missingIdsMap) {
	var data = missingIdsMap[currentId];
	this.insertElement(currentId, data);
    }
}


function method_insertMissingElementsSorted(missingIdsMap) {
    // We need to sort the missing ids to guarantee that elements are
    // not to be inserted behind other elements that are also missing!
    // In that case, nothing would be inserted, since the jquery selector
    // returns nothing.
    var sortedMissingIds = new Array();

    for ( var currentId in missingIdsMap) {
	sortedMissingIds.push(currentId);
    }

    sortedMissingIds.sort();

    for ( var idx in sortedMissingIds) {
	var currentId = sortedMissingIds[idx];
	var data = missingIdsMap[currentId];
	var previousSibling = this._getPreviousSiblingOf(currentId);
	this.insertElement(currentId, data, previousSibling);
    }
}

function method_fillAllIdsSorted(newElementIdMap) {
    this.allIdsSorted = new Array();
    for ( var currentId in newElementIdMap) {
	this.allIdsSorted.push(currentId);
    }
    this.allIdsSorted.sort();
}

function method_getPreviousSiblingOf(id) {
    var previousSibling = null;
    for ( var idx in this.allIdsSorted) {
	var currentId = this.allIdsSorted[idx];
	if (id == currentId) {
	    return previousSibling;
	}
	previousSibling = currentId;
    }
    return null;
}
